/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights
 * reserved. <a>http://www.cqcis.com</a>
 */
package com.cqcis.uip.base.logadaptor.impl;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Appender;
import org.apache.log4j.Category;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Priority;
import org.apache.log4j.spi.LoggingEvent;
import org.slf4j.Marker;
import org.slf4j.helpers.MarkerIgnoringBase;
import org.slf4j.helpers.MessageFormatter;
import org.slf4j.spi.LocationAwareLogger;

import com.cqcis.uip.base.logadaptor.AppenderAdapterable;
import com.cqcis.uip.base.logadaptor.LogConfiguration;
import com.cqcis.uip.base.logadaptor.LoggerAppenderRepository;

public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements
		LocationAwareLogger {
	private final static String FQCN = Log4jLoggerAdapter.class.getName();
	private final static String LOGLEVEL_DEFAULT = "DEFAULT";
	private final org.apache.log4j.Logger logger; // log4j的logger对象
	private LoggerAppenderRepository ar; // 线程(逻辑执行流)关联logger的appender对象集合
	
	public Log4jLoggerAdapter(org.apache.log4j.Logger logger) {
		this.logger = logger;
	}
	
	@SuppressWarnings("unchecked")
	private void clearAllLoggerAppenders() {
		Enumeration<Appender> oldAppenders = logger.getAllAppenders();
		List<Appender> olds = new ArrayList<Appender>();
		while (oldAppenders.hasMoreElements()) {
			olds.add(oldAppenders.nextElement());			
		}
		
		for (Appender old : olds) {
			logger.removeAppender(old);
		}
	}
	
	/**
	 * 日志
	 * 
	 * @param callerFQCN
	 * @param level
	 * @param message
	 * @param t
	 */
	@SuppressWarnings("unchecked")
	private synchronized void adaptorLog(String callerFQCN, Priority level,
			Object message, Throwable t) {	
		try {
		    forAdaptorLog(callerFQCN, level, message, t);
		} catch (Throwable et) {			
			et.printStackTrace(System.err);
		}
	}

	/**
	 * 日志
	 * 
	 * @param callerFQCN
	 * @param level
	 * @param message
	 * @param t
	 */
	@SuppressWarnings("unchecked")
	private void forAdaptorLog(String callerFQCN, Priority level,
			Object message, Throwable t) {				
		// 同时将日志继承的向上回溯设置为true, 使之没有appender时候能够利用父类的appender
		logger.setAdditivity(true);
        
		// 当前日志文件级别
		Level effectiveLevel = logger.getEffectiveLevel();
		
		List<LogConfiguration> logConfigs = ar.getLogConfigurations(Thread.currentThread());
		if (logConfigs != null && logConfigs.size() > 0) {
			logger.setAdditivity(false);
			for (LogConfiguration logConfig : logConfigs) {
				String logLevel = logConfig.getLevel();				
				if (logLevel.equalsIgnoreCase(LOGLEVEL_DEFAULT)) {
					effectiveLevel = logger.getEffectiveLevel();//默认
				} else {
					effectiveLevel = Level.toLevel(logLevel);
				}
				
				// 如果该日志被指定为在某个级别上关闭，则对所有日志组件都关闭。
				boolean disabled = !level.isGreaterOrEqual(logger.getEffectiveLevel());
								
				int appenderType = logConfig.getLogAppenderType();
								
				if (disabled && appenderType == LogConfiguration.LOGAPPENDER_TYPE_FILE) {
				    continue;
				}
				
				if (!level.isGreaterOrEqual(effectiveLevel)) {
					continue;
				}
								
				Appender appender = ar.createOrGetAppenderFor(logConfig, getTempleteFileAppender(logConfig));
				
				clearAllLoggerAppenders();// 清空所有的 appender
				
				logger.addAppender(appender);
				
				Set<Appender> others = getOldOtherAppenders();
				if (others != null && appenderType == LogConfiguration.LOGAPPENDER_TYPE_FILE) {
					for (Appender a : others) {
						logger.addAppender(a);
					}
				}
								
				// 写日志
				doLogWrite(callerFQCN, level, message, t);
			}			
		} else {
			// 必须先将所有的 appender 清空, 防止该日志被其他线程日志污染
			clearAllLoggerAppenders();
			// 写日志
			if (!logger.getLoggerRepository().isDisabled(level.toInt())
					&& level.isGreaterOrEqual(effectiveLevel)) {
				doLogWrite(callerFQCN, level, message, t);
			}			
		}
		
		clearAllLoggerAppenders();// 清空所有的 appender
	}
		
	private FileAppender getFileAppenderFromLogConfig(LogConfiguration logConfig) {
		Appender appender = logConfig.getLogObjectAppender();
		if (appender != null && appender instanceof FileAppender) {			
			return (FileAppender)appender;
		}
		
		return null;
	}
	
	private Set<Appender> getOldOtherAppenders() {
		Category log = this.logger;
		Set<Appender> others = new HashSet<Appender>();
		Set<Appender> fas = getOtherAppender(log);
		if (fas != null) {
			others.addAll(fas);
		}
		
		Category cate = null;		
		Category parent = log;
		while (true) {	
			if (parent == null) {
				break;
			}
			cate = parent;
			parent = parent.getParent();
		}
		
		if (cate == null) {
			return null;
		}
		
		Set<Appender> ofas = getOtherAppender(cate);
		if (ofas != null) {
			others.addAll(ofas);
		}
		return others;
	}
	
	@SuppressWarnings("unchecked")
	private Set<Appender> getOtherAppender(Category parent) {
		Set<Appender> others = new HashSet<Appender>();
		Enumeration<Appender> oldAppenders = parent.getAllAppenders();
		while (oldAppenders.hasMoreElements()) {
			Appender fa = oldAppenders.nextElement();
						
			if (!(fa instanceof FileAppender)) {
				others.add(fa);
			}
		}
		
		return others;
	}
	
	/**
	 * 获取模板appender
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	protected FileAppender getTempleteFileAppender(LogConfiguration logConfig) {	
		FileAppender appender = getFileAppenderFromLogConfig(logConfig);
		if (appender != null) {
			return (FileAppender)appender;
		}
		
		appender = getLoggerOldAppender(logger, logConfig);// check logger self
				
		if (appender != null) {
			return (FileAppender)appender;
		}
		
		throw new RuntimeException("Can't find Templete FileAppender, " +
		                         "please check 'log4j.properties' config.");
	}
	
	@SuppressWarnings("unchecked")
	private FileAppender getLoggerOldAppender(Category log, LogConfiguration logConfig) {
		FileAppender fa = getFileAppender(log);
		if (fa != null) {
			return fa;
		}
		
		Category cate = null;		
		Category parent = log;
		while (true) {	
			if (parent == null) {
				break;
			}
			cate = parent;
			parent = parent.getParent();
		}
		
		if (cate == null) {
			return null;
		}
		
		return getFileAppender(cate);
	}
	
	@SuppressWarnings("unchecked")
	private FileAppender getFileAppender(Category parent) {
		Enumeration<Appender> oldAppenders = parent.getAllAppenders();
		while (oldAppenders.hasMoreElements()) {
			Appender fa = oldAppenders.nextElement();
			
			if (fa instanceof AppenderAdapterable) {
				continue;
			}
			
			if (fa instanceof DailyRollingFileAppender) {
				return (FileAppender)fa;
			}
			
			if (fa instanceof FileAppender) {
				return (FileAppender)fa;
			}
		}
		
		return null;
	}
	
	/**
	 * 日志级别
	 * 
	 * @return
	 */
	private boolean isEnableLogLevel(Level willLevel) {
		boolean s = false;
		try {
		    s = forIsEnableLogLevel(willLevel);
		} catch (Throwable et) {			
		}
		
		return s;
	}
	
	/**
	 * 日志级别
	 * 
	 * @return
	 */
	private boolean forIsEnableLogLevel(Level willLevel) {
		List<LogConfiguration> logConfigs = ar.getLogConfigurations(Thread.currentThread());
		if (logConfigs != null) {			
			for (LogConfiguration logConfig : logConfigs) {
				Level effectiveLevel = null;
				String logLevel = logConfig.getLevel();
				if (logLevel.equalsIgnoreCase(LOGLEVEL_DEFAULT)) {
					effectiveLevel = logger.getEffectiveLevel();//默认
				} else {
					effectiveLevel = Level.toLevel(logLevel);
				}
				
				if (willLevel.isGreaterOrEqual(effectiveLevel)) {
					return true;
				}
			}
		}
		
		return logger.isEnabledFor(willLevel);
	}
	
	/**
	 * 分发日志事件到各日志组件
	 * 
	 * @param callerFQCN
	 * @param level
	 * @param message
	 * @param t
	 */
	@SuppressWarnings("unchecked")
	private void doLogWrite(String callerFQCN, Priority level, Object message, Throwable t) {
		LoggingEvent event = new LoggingEvent(callerFQCN, logger, level, message, t);
		
		// 直接分发事件到日志组件上
		try {
		    logger.callAppenders(event);		
		} catch (Throwable et) {
			System.err.println("Log error: " + et);
		}
	}

	public String getName() {
		return logger.getName();
	}

	public boolean isTraceEnabled() {		
		return isEnableLogLevel(Level.TRACE);
	}

	public void trace(String msg) {
		adaptorLog(FQCN, Level.TRACE, msg, null);
	}

	public void trace(String format, Object arg) {
		if (isTraceEnabled()) {
			String msgStr = MessageFormatter.format(format, arg);
			adaptorLog(FQCN, Level.TRACE, msgStr, null);
		}
	}

	public void trace(String format, Object arg1, Object arg2) {
		if (isTraceEnabled()) {
			String msgStr = MessageFormatter.format(format, arg1, arg2);
			adaptorLog(FQCN, Level.TRACE, msgStr, null);
		}
	}

	public void trace(String format, Object[] argArray) {
		if (isTraceEnabled()) {
			String msgStr = MessageFormatter.arrayFormat(format, argArray);
			adaptorLog(FQCN, Level.TRACE, msgStr, null);
		}
	}

	public void trace(String msg, Throwable t) {
		adaptorLog(FQCN, Level.TRACE, msg, t);
	}

	public boolean isDebugEnabled() {		
		return isEnableLogLevel(Level.DEBUG);
	}

	public void debug(String msg) {
		adaptorLog(FQCN, Level.DEBUG, msg, null);
	}

	public void debug(String format, Object arg) {
		if (isDebugEnabled()) {
			String msgStr = MessageFormatter.format(format, arg);
			adaptorLog(FQCN, Level.DEBUG, msgStr, null);
		}
	}

	public void debug(String format, Object arg1, Object arg2) {
		if (isDebugEnabled()) {
			String msgStr = MessageFormatter.format(format, arg1, arg2);
			adaptorLog(FQCN, Level.DEBUG, msgStr, null);
		}
	}

	public void debug(String format, Object[] argArray) {
		if (isDebugEnabled()) {
			String msgStr = MessageFormatter.arrayFormat(format, argArray);
			adaptorLog(FQCN, Level.DEBUG, msgStr, null);
		}
	}

	public void debug(String msg, Throwable t) {
		adaptorLog(FQCN, Level.DEBUG, msg, t);
	}

	public boolean isInfoEnabled() {		
		return isEnableLogLevel(Level.INFO);
	}

	public void info(String msg) {
		adaptorLog(FQCN, Level.INFO, msg, null);
	}

	public void info(String format, Object arg) {
		if (isInfoEnabled()) {
			String msgStr = MessageFormatter.format(format, arg);
			adaptorLog(FQCN, Level.INFO, msgStr, null);
		}
	}

	public void info(String format, Object arg1, Object arg2) {
		if (isInfoEnabled()) {
			String msgStr = MessageFormatter.format(format, arg1, arg2);
			adaptorLog(FQCN, Level.INFO, msgStr, null);
		}
	}

	public void info(String format, Object[] argArray) {
		if (isInfoEnabled()) {
			String msgStr = MessageFormatter.arrayFormat(format, argArray);
			adaptorLog(FQCN, Level.INFO, msgStr, null);
		}
	}

	public void info(String msg, Throwable t) {
		adaptorLog(FQCN, Level.INFO, msg, t);
	}

	public boolean isWarnEnabled() {		
		return isEnableLogLevel(Level.WARN);
	}

	public void warn(String msg) {
		adaptorLog(FQCN, Level.WARN, msg, null);
	}

	public void warn(String format, Object arg) {
		if (isWarnEnabled()) {
			String msgStr = MessageFormatter.format(format, arg);
			adaptorLog(FQCN, Level.WARN, msgStr, null);
		}
	}

	public void warn(String format, Object arg1, Object arg2) {
		if (isWarnEnabled()) {
			String msgStr = MessageFormatter.format(format, arg1, arg2);
			adaptorLog(FQCN, Level.WARN, msgStr, null);
		}
	}

	public void warn(String format, Object[] argArray) {
		if (isWarnEnabled()) {
			String msgStr = MessageFormatter.arrayFormat(format, argArray);
			adaptorLog(FQCN, Level.WARN, msgStr, null);
		}
	}

	public void warn(String msg, Throwable t) {
		adaptorLog(FQCN, Level.WARN, msg, t);
	}

	public boolean isErrorEnabled() {		
		return isEnableLogLevel(Level.ERROR);
	}

	public void error(String msg) {
		adaptorLog(FQCN, Level.ERROR, msg, null);
	}

	public void error(String format, Object arg) {
		if (isErrorEnabled()) {
			String msgStr = MessageFormatter.format(format, arg);
			adaptorLog(FQCN, Level.ERROR, msgStr, null);
		}
	}

	public void error(String format, Object arg1, Object arg2) {
		if (isErrorEnabled()) {
			String msgStr = MessageFormatter.format(format, arg1, arg2);
			adaptorLog(FQCN, Level.ERROR, msgStr, null);
		}
	}

	public void error(String format, Object[] argArray) {
		if (isErrorEnabled()) {
			String msgStr = MessageFormatter.arrayFormat(format, argArray);
			adaptorLog(FQCN, Level.ERROR, msgStr, null);
		}
	}

	public void error(String msg, Throwable t) {
		adaptorLog(FQCN, Level.ERROR, msg, t);
	}

	public void log(Marker marker, String callerFQCN, int level, String msg,
			Throwable t) {
		Level log4jLevel;
		switch (level) {
		case LocationAwareLogger.TRACE_INT:
			log4jLevel = Level.TRACE;
			break;
		case LocationAwareLogger.DEBUG_INT:
			log4jLevel = Level.DEBUG;
			break;
		case LocationAwareLogger.INFO_INT:
			log4jLevel = Level.INFO;
			break;
		case LocationAwareLogger.WARN_INT:
			log4jLevel = Level.WARN;
			break;
		case LocationAwareLogger.ERROR_INT:
			log4jLevel = Level.ERROR;
			break;
		default:
			throw new IllegalStateException("Level number " + level
					+ " is not recognized.");
		}
		adaptorLog(callerFQCN, log4jLevel, msg, t);
	}

	public void setAr(LoggerAppenderRepository ar) {
		this.ar = ar;
	}
}
